package com.xherorlclass;

import com.xherorlclass.annotation.XherorlRpcApi;
import com.xherorlclass.channelHandler.handler.MethodCallHandler;
import com.xherorlclass.channelHandler.handler.XherorlRpcRequestDecoder;
import com.xherorlclass.channelHandler.handler.XherorlRpcResponseEncoder;
import com.xherorlclass.config.Configuration;
import com.xherorlclass.core.HeartbeatDetector;
import com.xherorlclass.core.XherorlrpcShutdownHook;
import com.xherorlclass.discovery.RegistryConfig;
import com.xherorlclass.loadbalancer.LoadBalancer;
import com.xherorlclass.transport.message.XherorlRpcRequest;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LoggingHandler;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.net.InetSocketAddress;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * @author ：Xherorl
 * @date ：Created in 2023/8/24 9:40
 * @description：
 */
@Slf4j
@Data
public class XherorlRpcBootstrap {

    // XherorlRpcBootstrap 是个单例，我们希望每个应用程序只有一个实例
    private static final XherorlRpcBootstrap xherorlRpcBootstrap = new XherorlRpcBootstrap();

    // 全局的配置中心
    private final Configuration configuration;

    // 保存request对象，可以到当前线程中随时获取
    public static final ThreadLocal<XherorlRpcRequest> REQUEST_THREAD_LOCAL = new ThreadLocal<>();


    // 连接的缓存，如果使用InetSocketAddress这样的类做key，一定要看他们有没有重写equals和toString方法
    /*
    * equals() 方法：equals() 方法用于判断两个对象是否相等。如果没有正确实现 equals() 方法，就会使用默认的 Object 类的实现，它会比较对象的引用而不是内容。这意味着即使两个 InetSocketAddress 对象的 IP 地址和端口号相同，但由于引用不同，它们也被视为不相等的键。
     toString() 方法：toString() 方法返回对象的字符串表示。在使用对象作为键时，通常需要将对象转换为字符串进行存储和查找。如果没有正确实现 toString() 方法，会使用默认的 Object 类的实现，得到的是包含对象的类名和哈希码的字符串。这可能导致使用相同 IP 地址和端口号的不同 InetSocketAddress 对象产生不同的字符串表示，进而无法正确匹配键。
    * */
    public static final Map<InetSocketAddress, Channel> CHANNEL_CACHE = new ConcurrentHashMap<>(16);

    public static final TreeMap<Long, Channel> ANSWER_TIME_CHANNEL_CACHE = new TreeMap<>();

    // 维护已经发布且暴露的服务列表 key-> interface的全限定名  value -> ServiceConfig
    public static final Map<String, ServiceConfig<?>> SERVERS_LIST = new ConcurrentHashMap<>(16);

    // 定义全局的对外挂起的 completableFuture
    public static final Map<Long, CompletableFuture<Object>> PENDING_REQUEST = new ConcurrentHashMap<>(128);


    public XherorlRpcBootstrap() {
        // 构造启动程序时，需要做一些什么初始化的事
        configuration = new Configuration();
    }

    public static XherorlRpcBootstrap getInstance() {
        return xherorlRpcBootstrap;
    }

    /*
    * 用来定义当前应用的名字
    * */
    public XherorlRpcBootstrap application(String appName) {
        configuration.setAppName(appName);
        return this;
    }

    // 用来配置一个注册中心
    // registryConfig 注册中心
    public XherorlRpcBootstrap registry(RegistryConfig registryConfig) {
        // 这里维护一个zookeeper实例，但是，如果这样写就会将zookeeper和当前工程耦合
        // 我们其实是更希望以后可以扩展更多种不同的实现

        // 尝试使用 registryConfig 获取一个注册中心，有点工厂设计模式的意思了
        configuration.setRegistryConfig(registryConfig);
        return this;
    }

    // 配置负载均衡策略
    public XherorlRpcBootstrap loadBalancer(LoadBalancer loadBalancer){
        configuration.setLoadBalancer(loadBalancer);
        return this;
    }

    /**
     * ---------------------------服务提供方的相关api---------------------------------
     */

    // 发布服务，将接口 --> 实现，注册到服务中心
    // service 封装的需要发布的服务
    public XherorlRpcBootstrap publish(ServiceConfig<?> service){
        // 我们抽象了注册中心的概念，使用注册中心的一个实现完成注册
        // 有人会想，此时此刻难道不是强耦合了吗？
        configuration.getRegistryConfig().getRegistry().register(service);

        // 1、当服务调用方，通过接口、方法名、具体的方法参数列表发起调用，提供怎么知道使用哪一个实现
        // (1) new 一个  （2）spring beanFactory.getBean(Class)  (3) 自己维护映射关系
        SERVERS_LIST.put(service.getInterface().getName(), service);
        return this;
    }

    // 批量发布
    // services 封装的需要发布的服务集合
    public XherorlRpcBootstrap publish(List<ServiceConfig<?>> services) {
        for (ServiceConfig<?> service : services) {
            this.publish(service);
        }
        return this;
    }

    /*
    * 启动 netty 服务
    * */
    public void start(){

        // 注册关闭应用程序的钩子函数
        Runtime.getRuntime().addShutdownHook(new XherorlrpcShutdownHook());

        // 1、创建eventLoop，老板只负责处理请求，之后会将请求分发至worker
        EventLoopGroup boss = new NioEventLoopGroup(2);
        EventLoopGroup worker = new NioEventLoopGroup(10);
        try {

            // 2、需要一个服务器引导程序
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            // 3、配置服务器
            serverBootstrap = serverBootstrap.group(boss, worker)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            // 是核心，我们需要添加很多入栈和出栈的handler
                            socketChannel.pipeline()
                                    .addLast(new LoggingHandler())
                                    .addLast(new XherorlRpcRequestDecoder())
                                    // 根据请求执行方法调用
                                    .addLast(new MethodCallHandler())
                                    .addLast(new XherorlRpcResponseEncoder());
                        }
                    });

            // 4、绑定端口
            ChannelFuture channelFuture = serverBootstrap.bind(configuration.getPort()).sync();

            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            try {
                boss.shutdownGracefully().sync();
                worker.shutdownGracefully().sync();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }




    /**
     * ---------------------------服务调用方的相关api---------------------------------
     */
    public XherorlRpcBootstrap reference(ReferenceConfig<?> reference) {

        // 开启对这个服务的心跳检测
        HeartbeatDetector.detectHeartbeat(reference.getInterface().getName());

        // 在这个方法里我们是否可以拿到相关的配置项-注册中心
        // 配置reference，将来调用get方法时，方便生成代理对象
        // 1、reference需要一个注册中心
        reference.setRegistry(configuration.getRegistryConfig().getRegistry());
        reference.setGroup(this.getConfiguration().getGroup());

        return this;
    }


    /*
    * 配置序列化的方式
    * */
    public XherorlRpcBootstrap serialize(String serializeType) {
       configuration.setSerializeType(serializeType);
        if (log.isDebugEnabled()){
            log.debug("我们配置了使用的序列化的方式为【{}】。", serializeType);
        }
        return this;
    }

    public XherorlRpcBootstrap compress(String compressType) {
        configuration.setCompressType(compressType);
        if (log.isDebugEnabled()) {
            log.debug("我们配置了使用的压缩算法为【{}】。", compressType);
        }
        return this;
    }

    /**
     * 扫描包，进行批量注册
     * @param packageName 包名
     * @return  this本身
     */
    public XherorlRpcBootstrap scan(String packageName) {

        // 1、需要通过packageName获取其下的所有的类的权限定名称
        List<String> classNames = getAllClassNames(packageName);
        // 2、通过反射获取他的接口，构建具体实现
        List<Class<?>> classes = classNames.stream()
                .map(className -> {
                    try {
                        return Class.forName(className);
                    } catch (ClassNotFoundException e) {
                        throw new RuntimeException(e);
                    }
                }).filter(clazz -> clazz.getAnnotation(XherorlRpcApi.class) != null).collect(Collectors.toList());

        for (Class<?> clazz : classes) {
            // 获取它的接口
            Class<?>[] interfaces = clazz.getInterfaces();
            Object instance = null;
            try {
                instance = clazz.getConstructor().newInstance();
            }catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e){
                throw new RuntimeException(e);
            }

            // 获取分组信息
            XherorlRpcApi xherorlRpcApi = clazz.getAnnotation(XherorlRpcApi.class);

            String group = xherorlRpcApi.group();

            for (Class<?> anInterface : interfaces) {
                ServiceConfig<?> serviceConfig = new ServiceConfig<>();
                serviceConfig.setInterface(anInterface);
                serviceConfig.setRef(instance);
                serviceConfig.setGroup(group);

                if (log.isDebugEnabled()){
                    log.debug("---->已经通过包扫描，将服务【{}】发布.",anInterface);
                }
                // 3、发布
                publish(serviceConfig);
            }
        }
        return this;
    }

    private List<String> getAllClassNames(String packageName) {
        // 1、通过packageName获得绝对路径
        // com.xherorlclass.xxx.yyy -> E://xxx/xww/sss/com/xherorlclass/xxx/yyy

        String basePath = packageName.replaceAll("\\.","/"); //  basePath = com/xherorlclass

        URL url = ClassLoader.getSystemClassLoader().getResource(basePath);  // url = file:/D:/JavaProject/xherorl-rpc/xherorl-framework/xherorl-core/target/classes/com/xherorlclass

        if(url == null){
            throw new RuntimeException("包扫描时，发现路径不存在.");
        }

        String absolutePath = url.getPath();  // absolutePath = /D:/JavaProject/xherorl-rpc/xherorl-framework/xherorl-core/target/classes/com/xherorlclass

        List<String> classNames = new ArrayList<>();

        // 递归读取文件
        classNames = recursionFile(absolutePath,classNames,basePath);

        return classNames;

    }

    private List<String> recursionFile(String absolutePath, List<String> classNames, String basePath) {

        // 获取文件
        File file = new File(absolutePath);
        // 判断文件是否是文件夹
        if (file.isDirectory()){
            // 找到文件夹下所有的文件
            File[] children = file.listFiles(pathname -> pathname.isDirectory() || pathname.getPath().contains(".class"));

            if (children == null || children.length == 0){
                return classNames;
            }

            for (File child : children) {
                if (child.isDirectory()){
                    // 递归调用
                    recursionFile(child.getAbsolutePath(),classNames,basePath);
                }else {
                    // 文件 --> 类的权限定名称
                    String className = getClassNameByAbsolutePath(child.getAbsolutePath(),basePath);
                    classNames.add(className);
                }
            }
        }else {
            // 文件 --> 类的权限定名称
            String className = getClassNameByAbsolutePath(absolutePath,basePath);
            classNames.add(className);
        }
        return classNames;
    }

    private String getClassNameByAbsolutePath(String absolutePath, String basePath) {

        String fileName = absolutePath.substring(absolutePath.indexOf(basePath.replaceAll("/", "\\\\"))).replaceAll("\\\\", ".");

        fileName = fileName.substring(0, fileName.indexOf(".class"));

        return fileName;
    }

    public Configuration getConfiguration() {
        return configuration;
    }

    public XherorlRpcBootstrap group(String group) {
        this.getConfiguration().setGroup(group);
        return this;
    }

    public static void main(String[] args) {
        List<String> allClassNames = XherorlRpcBootstrap.getInstance().getAllClassNames("com.xherorlclass");
        System.out.println(allClassNames);
    }

}
